﻿using System;
using System.Collections.Generic;
using System.DirectoryServices;
using System.Globalization;
using System.Linq;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
using System.Web;
using System.Web.Script.Serialization;
using Microsoft.SharePoint.Client.Services;

namespace Practical.SharePoint.Base.WebServices
{
    [BasicHttpBindingServiceMetadataExchangeEndpoint]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
    public class ADQuery : IADQuery, IDisposable
    {
        private static readonly string[] DEFAULTDIRECTORYSEARCHPROPERTIESTOLOAD = {"name", "objectClass", "member", "memberof", "cn"};
        private const string DIRECTORYSEARCHERFILTER = "(&(objectClass=*)(|(cn={0})(sAMAccountName={0})))";
        private const string MEMBEROFTHEFOLLOWINGGROUPS = @"""{0}"" is a member of the following groups: ";
        private const string HASTHEFOLLOWINGGROUPMEMBERS = @"""{0}"" has the following group members: ";
        private const string FOLLOWINGITEMSMATCHEDYOURREQUEST = @"The following items matched your request of ""{0}"". Please refine your search: ";
        readonly JavaScriptSerializer _serializer = new JavaScriptSerializer();
        readonly DirectoryEntry _directoryEntry = new DirectoryEntry();
        readonly DirectorySearcher _directorySearcher = new DirectorySearcher();

        [WebInvoke(
            Method = "POST",
            BodyStyle = WebMessageBodyStyle.WrappedRequest,
            RequestFormat = WebMessageFormat.Json,
            ResponseFormat = WebMessageFormat.Json)]
        string IADQuery.QueryActiveDirectory(string adQueryString)
        {
            return QueryActiveDirectory(adQueryString);
        }

        protected string QueryActiveDirectory(string adQueryString)
        {
            adQueryString = HttpUtility.UrlDecode(adQueryString);
            if (adQueryString.Contains(@"\"))
            {
                adQueryString = adQueryString.Substring(adQueryString.IndexOf(@"\", StringComparison.OrdinalIgnoreCase) + 1);
            }
            ADResults adResults = new ADResults();

            _directorySearcher.PageSize = 50;
            _directorySearcher.SearchRoot = _directoryEntry;
            _directorySearcher.SearchScope = SearchScope.Subtree;
            _directorySearcher.ReferralChasing = ReferralChasingOption.All;
            _directorySearcher.Filter = string.Format(CultureInfo.InvariantCulture, DIRECTORYSEARCHERFILTER, adQueryString);
            _directorySearcher.PropertiesToLoad.AddRange(DEFAULTDIRECTORYSEARCHPROPERTIESTOLOAD);

            SearchResultCollection searchResults = _directorySearcher.FindAll();
            if (searchResults.Count == 1)
            {
                SearchResult searchResult = searchResults[0];
                if (searchResult != null)
                {
                    if (searchResult.Properties["objectclass"].Contains("user"))
                    {
                        adResults.Description = string.Format(CultureInfo.CurrentUICulture, MEMBEROFTHEFOLLOWINGGROUPS, searchResult.Properties["cn"][0]);
                        foreach (DirectoryEntry memberOfDirectoryEntry in from string memberOf in searchResult.Properties["memberof"] select new DirectoryEntry("LDAP://" + memberOf))
                        {
                            adResults.Results.Add(new ADResult { Title = memberOfDirectoryEntry.Properties["cn"].Value.ToString() });
                        }
                    }
                    else if (searchResult.Properties["objectclass"].Contains("group"))
                    {
                        adResults.Description = string.Format(CultureInfo.CurrentUICulture, HASTHEFOLLOWINGGROUPMEMBERS, searchResult.Properties["cn"][0]);
                        foreach (DirectoryEntry memberOfDirectoryEntry in from string member in searchResult.Properties["member"] select new DirectoryEntry("LDAP://" + member))
                        {
                            adResults.Results.Add(new ADResult { Title = memberOfDirectoryEntry.Properties["cn"].Value.ToString() });
                        }
                    }
                }
            }
            else if (searchResults.Count > 1)
            {
                adResults.Description = string.Format(CultureInfo.CurrentUICulture, FOLLOWINGITEMSMATCHEDYOURREQUEST, adQueryString);
                foreach (SearchResult searchResult in searchResults)
                {
                    if (searchResult.Properties["objectclass"].Contains("user"))
                    {
                        adResults.Results.Add(new ADResult { Title = searchResult.Properties["cn"][0].ToString() });

                    }
                    else if (searchResult.Properties["objectclass"].Contains("group"))
                    {
                        adResults.Results.Add(new ADResult { Title = searchResult.Properties["cn"][0].ToString() });
                    }
                }
            }

            return _serializer.Serialize(adResults);
        }

        [WebInvoke(
            Method = "POST",
            BodyStyle = WebMessageBodyStyle.WrappedRequest,
            RequestFormat = WebMessageFormat.Json,
            ResponseFormat = WebMessageFormat.Json)]
        string IADQuery.QueryActiveDirectoryWithProperties(string adQueryString, string propertiesToLoad)
        {
            return QueryActiveDirectoryWithProperties(adQueryString, propertiesToLoad);
        }

        protected string QueryActiveDirectoryWithProperties(string adQueryStringInput, string propertiesToLoadInput)
        {
            List<string> directorySearchPropertiesList = new List<string>(HttpUtility.UrlDecode(propertiesToLoadInput).Split(new[] { ';' },StringSplitOptions.RemoveEmptyEntries));

            string adQueryString = HttpUtility.UrlDecode(adQueryStringInput);
            if (adQueryString.Contains(@"\"))
            {
                adQueryString = adQueryString.Substring(adQueryString.IndexOf(@"\", StringComparison.OrdinalIgnoreCase) + 1);
            }

            ADResults adResults = new ADResults();

            _directorySearcher.PageSize = 50;
            _directorySearcher.SearchRoot = _directoryEntry;
            _directorySearcher.SearchScope = SearchScope.Subtree;
            _directorySearcher.ReferralChasing = ReferralChasingOption.All;
            _directorySearcher.Filter = string.Format(CultureInfo.InvariantCulture, DIRECTORYSEARCHERFILTER, adQueryStringInput);
            _directorySearcher.PropertiesToLoad.AddRange(DEFAULTDIRECTORYSEARCHPROPERTIESTOLOAD);
            _directorySearcher.PropertiesToLoad.AddRange(directorySearchPropertiesList.ToArray());

            SearchResultCollection searchResults = _directorySearcher.FindAll();
            if (searchResults.Count == 1)
            {
                SearchResult searchResult = searchResults[0];
                if (searchResult != null)
                {
                    if (searchResult.Properties["objectclass"].Contains("user"))
                    {
                        adResults.Description = string.Format(CultureInfo.CurrentUICulture, MEMBEROFTHEFOLLOWINGGROUPS, searchResult.Properties["cn"][0]);
                        foreach (DirectoryEntry memberOfDirectoryEntry in from string memberOf in searchResult.Properties["memberof"] select new DirectoryEntry("LDAP://" + memberOf))
                        {
                            adResults.Results.Add(new ADResult
                            {
                                Title = memberOfDirectoryEntry.Properties["cn"].Value.ToString()
                            });
                        }

                        for (int index = 0; index < directorySearchPropertiesList.Count; index++)
                        {
                            KeyValuePair<string, object> kvp = new KeyValuePair<string, object>();
                            kvp.Key = directorySearchPropertiesList[index];
                            try
                            {
                                var securityIdentifer = searchResult.Properties[kvp.Key][0] as byte[];
                                if (securityIdentifer != null)
                                {
                                    kvp.Value = StringHelpers.GetSecurityIdentiferString(securityIdentifer);
                                }
                                else
                                {
                                    throw new FormatException("Security identifer is not valid.");
                                }
                            }
                            catch (FormatException)
                            {
                                kvp.Value = string.Empty;
                            }
                            
                            adResults.PropertiesList.Add(kvp);
                        }
                    }
                    else if (searchResult.Properties["objectclass"].Contains("group"))
                    {
                        adResults.Description = string.Format(CultureInfo.CurrentUICulture, HASTHEFOLLOWINGGROUPMEMBERS, searchResult.Properties["cn"][0]);
                        foreach (DirectoryEntry memberOfDirectoryEntry in from string member in searchResult.Properties["member"] select new DirectoryEntry("LDAP://" + member))
                        {
                            adResults.Results.Add(new ADResult
                            {
                                Title = memberOfDirectoryEntry.Properties["cn"].Value.ToString()
                            });
                        }
                    }
                }
            }
            else if (searchResults.Count > 1)
            {
                adResults.Description = string.Format(CultureInfo.CurrentUICulture, FOLLOWINGITEMSMATCHEDYOURREQUEST, adQueryStringInput);
                foreach (SearchResult searchResult in searchResults)
                {
                    if (searchResult.Properties["objectclass"].Contains("user"))
                    {
                        adResults.Results.Add(new ADResult { Title = searchResult.Properties["cn"][0].ToString() });

                    }
                    else if (searchResult.Properties["objectclass"].Contains("group"))
                    {
                        adResults.Results.Add(new ADResult { Title = searchResult.Properties["cn"][0].ToString() });
                    }
                }
            }

            return _serializer.Serialize(adResults);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposeManaged)
        {
            if (disposeManaged)
            {
                if (_directoryEntry != null)
                {
                    _directoryEntry.Dispose();
                }
                if (_directorySearcher != null)
                {
                    _directorySearcher.Dispose();
                }
            }
        }
    }
}